﻿using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Model.DataManagement;
using Model.Entity;

namespace LibraryManagerTests.Model.DataManagement
{

    /// <summary>
    /// This class contains unit tests for Model.DataManagement.LibrarianManager.
    /// </summary>
    [TestClass]
    public class LibrarianManagerTests
    {

        class TestableLibrarianManager : LibrarianManager
        {
            public static Int32 FirstId { get { return FIRST_ID; } }

            public IList<Librarian> Entities { get { return _entities; } }

            public IList<Librarian> Edited { get { return _edited; } }

            public IList<Librarian> New { get { return _new; } }

            public IList<Librarian> Deleted { get { return _deleted; } }

            public IList<Librarian> Managed { get { return _managed; } }

            public string GetMD5Hash(string pass) {
                return LibrarianManager.GetMd5Hash(pass);
            }

        }

        /// <summary>
        /// Tests LibrarianManager.CanSave().
        /// </summary>
        [TestMethod]
        public void TestCanSave()
        {

            // obtaining references for less text
            ushort nameMax = LibrarianManager.MAX_NAME_LENGTH;
            string nameErr = LibrarianManager.NAME_LEN_ERROR_HU;
            ushort addrMax = LibrarianManager.MAX_ADDR_LENGTH;
            string addrErr = LibrarianManager.ADDR_LEN_ERROR_HU;
            ushort phoneMax = LibrarianManager.MAX_PHONE_LENGTH;
            string phoneErr = LibrarianManager.PHONE_LEN_ERROR_HU;
            ushort userMax = LibrarianManager.MAX_USER_LENGTH;
            string userErr = LibrarianManager.USER_LEN_ERROR_HU;
            ushort passMax = LibrarianManager.MAX_PASS_LENGTH;
            string passErr = LibrarianManager.PASS_LEN_ERROR_HU;

            TestableLibrarianManager tmm = new TestableLibrarianManager();
            Librarian s = new Librarian(1);
            Messages msg;

            s.Username = "x";
            s.Password = "x";
            //
            // name length validation
            //

            // valid names
            for (int i = 0; i <= nameMax; i++)
            {
                s.Name = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid names
            for (int i = nameMax + 1; i < nameMax * 2; i++)
            {
                s.Name = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);
                string expectedMsg = string.Format(nameErr, i, nameMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }


            //
            // address length validation
            //
            s.Name = "";

            // valid addresses
            for (int i = 0; i <= addrMax; i++)
            {
                s.Address = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid addresses
            for (int i = addrMax + 1; i < addrMax * 2; i++)
            {
                s.Address = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);
                string expectedMsg = string.Format(addrErr, i, addrMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // phone length validation
            //
            s.Address = "";

            // valid phones
            for (int i = 0; i <= phoneMax; i++)
            {
                s.Phone = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid phones
            for (int i = phoneMax + 1; i < phoneMax * 2; i++)
            {
                s.Phone = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);
                string expectedMsg = string.Format(phoneErr, i, phoneMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // username length validation
            //
            s.Phone = "";

            // valid usernames
            for (int i = 1; i <= userMax; i++)
            {
                s.Username = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid usernames
            for (int i = userMax + 1; i < userMax * 2; i++)
            {
                s.Username = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);
                string expectedMsg = string.Format(userErr, i, userMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            // null, "" username
            msg = tmm.CanSave(new Librarian(2, null, "pass", "", "", "", false));
            Assert.AreEqual(1, msg.MessageList.Count);
            Assert.AreEqual(LibrarianManager.USER_MISSING_ERROR_HU, msg.MessageList[0]);
            msg = tmm.CanSave(new Librarian(2, "", "pass", "", "", "", false));
            Assert.AreEqual(1, msg.MessageList.Count);
            Assert.AreEqual(LibrarianManager.USER_MISSING_ERROR_HU, msg.MessageList[0]);

            // already used username
            var l = new Librarian(2, "taken", "x", "", "", "", false);
            tmm.Entities.Add(l);
            tmm.Managed.Add(l);
            msg = tmm.CanSave(new Librarian(null, "taken", "pass", "", "", "", false));
            Assert.AreEqual(1, msg.MessageList.Count);
            string expected = string.Format(LibrarianManager.USER_INUSE_ERROR_HU, "taken");
            Assert.AreEqual(expected, msg.MessageList[0]);
            msg = tmm.CanSave(new Librarian(2, "nottaken", "pass", "", "", "", false));
            Assert.AreEqual(0, msg.MessageList.Count);


            //
            // password length validation
            //
            s.Username = "x";

            // valid passwords
            for (int i = 1; i <= passMax; i++)
            {
                s.Password = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid passwords
            for (int i = passMax + 1; i < passMax * 2; i++)
            {
                s.Password = string.Concat(Enumerable.Repeat("x", i));
                msg = tmm.CanSave(s);
                string expectedMsg = string.Format(passErr, i, passMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            // null or "" password
            msg = tmm.CanSave(new Librarian(2, "asdf", null, "", "", "", false));
            Assert.AreEqual(1, msg.MessageList.Count);
            Assert.AreEqual(LibrarianManager.PASS_MISSING_ERROR_HU, msg.MessageList[0]);

            msg = tmm.CanSave(new Librarian(2, "asdf", "", "", "", "", false));
            Assert.AreEqual(1, msg.MessageList.Count);
            Assert.AreEqual(LibrarianManager.PASS_MISSING_ERROR_HU, msg.MessageList[0]);

            //
            // compound length validation
            //

            ushort tests = 200; // the more the better?
            Random r = new Random();
            ushort nameLen;
            string nameMsg;
            ushort addrLen;
            string addrMsg;
            ushort phoneLen;
            string phoneMsg;
            ushort userLen;
            string userMsg;
            ushort passLen;
            string passMsg;
            ushort count;

            ushort invalidNames = 0;
            ushort invalidAddresses = 0;
            ushort invalidPhones = 0;
            ushort invalidUsernames = 0;
            ushort invalidPasswords = 0;
            for (int i = 0; i < tests; i++)
            {

                // generate random length name
                nameLen = (ushort) r.Next(nameMax * 2);
                s.Name = string.Concat(Enumerable.Repeat("x", nameLen));

                // generate random length address
                addrLen = (ushort) r.Next(addrMax * 2);
                s.Address = string.Concat(Enumerable.Repeat("x", addrLen));

                // generate random length phone
                phoneLen = (ushort) r.Next(phoneMax * 2);
                s.Phone = string.Concat(Enumerable.Repeat("x", phoneLen));

                // generate random length username
                userLen = (ushort) r.Next(userMax * 2);
                s.Username = string.Concat(Enumerable.Repeat("x", userLen));

                // generate random length password
                passLen = (ushort) r.Next(passMax * 2);
                s.Password = string.Concat(Enumerable.Repeat("x", passLen));

                msg = tmm.CanSave(s);
                count = 0;

                // check name validation
                nameMsg = string.Format(nameErr, nameLen, nameMax);
                if (nameLen > nameMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(nameMsg));
                    count++;
                    invalidNames++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(nameMsg));
                }

                // check address validation
                addrMsg = string.Format(addrErr, addrLen, addrMax);
                if (addrLen > addrMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(addrMsg));
                    count++;
                    invalidAddresses++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(addrMsg));
                }

                // check phone validation
                phoneMsg = string.Format(phoneErr, phoneLen, phoneMax);
                if (phoneLen > phoneMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(phoneMsg));
                    count++;
                    invalidPhones++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(phoneMsg));
                }

                // check username validation
                userMsg = string.Format(userErr, userLen, userMax);
                if (userLen > userMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(userMsg));
                    count++;
                    invalidUsernames++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(userMsg));
                }

                // check password validation
                passMsg = string.Format(passErr, passLen, passMax);
                if (passLen > passMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(passMsg));
                    count++;
                    invalidPasswords++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(passMsg));
                }

            }

            // fail the test if there was a non-tested case
            Assert.IsTrue(invalidNames > 0 && invalidNames < tests);
            Assert.IsTrue(invalidAddresses > 0 && invalidAddresses < tests);
            Assert.IsTrue(invalidPhones > 0 && invalidPhones < tests);
            Assert.IsTrue(invalidUsernames > 0 && invalidUsernames < tests);
            Assert.IsTrue(invalidPasswords > 0 && invalidPasswords < tests);

        }

        /// <summary>
        /// Tests LibrarianManager.FindLibrarian().
        /// </summary>
        [TestMethod]
        public void TestFindLibrarian()
        {
            TestableLibrarianManager tmm = new TestableLibrarianManager();
            Librarian l;

            var l1 = new Librarian(1, "user1", tmm.GetMD5Hash("pass1"), "", "", "");
            tmm.Entities.Add(l1);
            tmm.Managed.Add(l1);
            var l2 = new Librarian(2, "user2", tmm.GetMD5Hash("pass4"), "", "", "");
            tmm.Entities.Add(l2);
            tmm.Managed.Add(l2);
            var l3 = new Librarian(3, "user3", tmm.GetMD5Hash("pass3"), "", "", "");
            tmm.Entities.Add(l3);
            tmm.Managed.Add(l3);

            Assert.AreEqual(l1, tmm.FindLibrarian("user1", "pass1"));
            Assert.AreEqual(l2, tmm.FindLibrarian("user2", "pass4"));
            Assert.AreEqual(l3, tmm.FindLibrarian("user3", "pass3"));

            Assert.IsNull(tmm.FindLibrarian("user1", "pass3"));
            Assert.IsNull(tmm.FindLibrarian("user2", "pass1"));
            Assert.IsNull(tmm.FindLibrarian("user3", "pass4"));

        }

        /// <summary>
        /// Tests LibrarianManager.Marshal(), LibrarianManager.Unmarshal().
        /// </summary>
        [TestMethod]
        public void TestMarshalUnmarshal()
        {
            int tests = 1000;
            TestableLibrarianManager lm = new TestableLibrarianManager();
            Librarian l;
            Librarian u; // unmarshalled
            Random r = new Random();
            RandomUnicodeString randUniStr = new RandomUnicodeString();

            /*
             * Tests by fields
             */

            // id
            Int32 id;
            for (int i = 0; i < tests; i++)
            {
                id = r.Next(Int32.MinValue, Int32.MaxValue);
                l = new Librarian(id);
                Assert.AreEqual(id, lm.Unmarshal(lm.Marshal(l)).GetId());
            }

            // name
            string name;
            var maxName = LibrarianManager.MAX_NAME_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                name = randUniStr.GetString(r.Next(maxName * 2));
                l = new Librarian(0);
                l.Name = name;
                u = lm.Unmarshal(lm.Marshal(l));
                if (name.Length > maxName) { name = name.Substring(0, maxName); }
                Assert.AreEqual(name, u.Name);
            }

            // address
            string address;
            var maxAddress = LibrarianManager.MAX_ADDR_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                address = randUniStr.GetString(r.Next(maxAddress * 2));
                l = new Librarian(0);
                l.Address = address;
                u = lm.Unmarshal(lm.Marshal(l));
                if (address.Length > maxAddress) { address = address.Substring(0, maxAddress); }
                Assert.AreEqual(address, u.Address);
            }

            // phone
            string phone;
            var maxPhone = LibrarianManager.MAX_PHONE_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                phone = randUniStr.GetString(r.Next(maxPhone * 2));
                l = new Librarian(0);
                l.Phone = phone;
                u = lm.Unmarshal(lm.Marshal(l));
                if (phone.Length > maxPhone) { phone = phone.Substring(0, maxPhone); }
                Assert.AreEqual(phone, u.Phone);
            }

            // isAdmin
            l = new Librarian(0);
            bool isAdmin = true;
            for (int i = 0; i < tests; i++)
            {
                if (r.Next() % 2 == 0) { isAdmin ^= true; }
                l.IsAdmin = isAdmin;
                Assert.AreEqual(isAdmin, lm.Unmarshal(lm.Marshal(l)).IsAdmin);
            }

            // username
            string username;
            var maxUsername = LibrarianManager.MAX_USER_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                username = randUniStr.GetString(r.Next(maxUsername * 2));
                l = new Librarian(0);
                l.Username = username;
                u = lm.Unmarshal(lm.Marshal(l));
                if (username.Length > maxUsername) { username = username.Substring(0, maxUsername); }
                Assert.AreEqual(username, u.Username);
            }

            // password
            string password;
            var maxPassword = LibrarianManager.MAX_PASS_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                password = randUniStr.GetString(r.Next(1, maxPassword * 2));
                password = lm.GetMD5Hash(password);
                l = new Librarian(0);
                l.Password = password;
                u = lm.Unmarshal(lm.Marshal(l));
                if (password.Length > maxPassword) { password = password.Substring(0, maxPassword); }
                Assert.AreEqual(password, u.Password);
            }


            /*
             * Compound fields test
             */

            byte[] m; // marshalled
            for (int i = 0; i < tests; i++)
            {
                id = r.Next(Int32.MinValue, Int32.MaxValue);
                name = randUniStr.GetString(r.Next(maxName * 2));
                address = randUniStr.GetString(r.Next(maxAddress * 2));
                phone = randUniStr.GetString(r.Next(maxPhone * 2));
                if (r.Next() % 2 == 0) { isAdmin ^= true; }
                username = randUniStr.GetString(r.Next(maxUsername * 2));
                password = randUniStr.GetString(r.Next(maxPassword * 2));

                l = new Librarian(id, username, password, name, address, phone, isAdmin);
                m = lm.Marshal(l);
                Assert.AreEqual((uint) m.Length, lm.GetSerializedEntityLength());

                u = lm.Unmarshal(m);

                if (password.Length > maxPassword) { password = password.Substring(0, maxPassword); }
                if (username.Length > maxUsername) { username = username.Substring(0, maxUsername); }
                if (phone.Length > maxPhone) { phone = phone.Substring(0, maxPhone); }
                if (address.Length > maxAddress) { address = address.Substring(0, maxAddress); }
                if (name.Length > maxName) { name = name.Substring(0, maxName); }
                Assert.AreEqual(password, u.Password);
                Assert.AreEqual(username, u.Username);
                Assert.AreEqual(isAdmin, l.IsAdmin);
                Assert.AreEqual(phone, u.Phone);
                Assert.AreEqual(address, u.Address);
                Assert.AreEqual(name, u.Name);
                Assert.AreEqual(id, lm.Unmarshal(lm.Marshal(l)).GetId());
            }
        }

        /// <summary>
        /// Tests LibrarianManager.LoadEntities(), PersistEntities().
        /// </summary>
        [TestMethod]
        public void TestLoadPersist()
        {
            // entities to persist
            int entities = 100;
            Random r = new Random();
            TestableLibrarianManager tlmP = new TestableLibrarianManager();

            Int32 id;
            string name;
            var maxName = LibrarianManager.MAX_NAME_LENGTH;
            string address;
            var maxAddress = LibrarianManager.MAX_ADDR_LENGTH;
            string phone;
            var maxPhone = LibrarianManager.MAX_PHONE_LENGTH;
            bool isAdmin = true;
            string username;
            var maxUsername = LibrarianManager.MAX_USER_LENGTH;
            string password;
            var maxPassword = LibrarianManager.MAX_PASS_LENGTH;

            // generate random entities
            var randUnicodeStr = new RandomUnicodeString();
            for (int i = 0; i < entities; i++)
            {
                id = i + TestableLibrarianManager.FirstId;
                name = randUnicodeStr.GetString(r.Next(maxName * 2));
                address = randUnicodeStr.GetString(r.Next(maxAddress * 2));
                phone = randUnicodeStr.GetString(r.Next(maxPhone * 2));
                if (r.Next() % 2 == 0) { isAdmin ^= true; }
                username = randUnicodeStr.GetString(r.Next(maxUsername * 2));
                password = randUnicodeStr.GetString(r.Next(maxPassword * 2));

                Librarian l = new Librarian(id, username, password, name, address, phone, isAdmin);

                // place the entity into one of the managed lists, based on i's value
                tlmP.Managed.Add(l);
                switch (i % 4)
                {
                    case 0:
                        tlmP.Entities.Add(l);
                        break;

                    case 1:
                        tlmP.Edited.Add(l);
                        break;

                    case 2:
                        tlmP.New.Add(l);
                        break;

                    case 3:
                        tlmP.Deleted.Add(l);
                        break;
                }
            }

            // persist the entities & wait for the os
            tlmP.PersistEntities();
            Thread.Sleep(1000);

            // load them back
            TestableLibrarianManager tlmL = new TestableLibrarianManager();
            tlmL.LoadEntities();

            // test the results
            //

            // list counts
            Assert.AreEqual(tlmP.Managed.Count - tlmP.Deleted.Count, tlmL.Managed.Count);
            Assert.AreEqual(tlmP.Entities.Count + tlmP.Edited.Count + tlmP.New.Count, tlmL.Entities.Count);
            Assert.AreEqual(0, tlmL.Edited.Count);
            Assert.AreEqual(0, tlmL.New.Count);
            Assert.AreEqual(0, tlmL.Deleted.Count);

            // list values
            foreach (Librarian l in tlmP.Entities)
            {
                bool found = false;
                foreach (Librarian o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }
            foreach (Librarian l in tlmP.Edited)
            {
                bool found = false;
                foreach (Librarian o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }
            foreach (Librarian l in tlmP.New)
            {
                bool found = false;
                foreach (Librarian o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }

            var list = tlmP.Managed.Where(item => !(tlmP.Deleted.Contains(item))).ToList();
            foreach (Librarian l in tlmL.Managed)
            {
                bool found = false;
                foreach (Librarian o in list)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }

            // delete the file
            File.Delete(tlmP.GetDatafileName());
        }
    }
}
